#
#   GameObjectManager.py
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; version 2 of the License.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   Copyright
#   Author: Nicholas F. Hoover
#   Contributors: Salvatore S. Gionfriddo
#   Created: 2007.06.20
#   Last Modified: 2007.11.18
#

import pygame, random
import GameObject, Settings
from Engine import Engine, SpriteList
from Hero import Hero
from Flame import Flame, Gas
from PowerUp import PowerUp
from Parachute import Parachute, Hole
from Projectile import Projectile
from Statistics import Statistics, EG_Stats

class GameObjectManager(Engine):

    NUM_SPRITE_LISTS = 8
    #NOTE: This is the update and draw order
    (POWERUPS, HOLES, KIDS, FLAMES, GASES, HERO, PROJECTILES, PKIDS) = range(NUM_SPRITE_LISTS)
    
    def __init__(self, drawSurface, resourceManager, hero = 'EvilGreg'):
        Engine.__init__(self, drawSurface)
        self.resourceManager = resourceManager
        self.setNumberSpriteLists(self.NUM_SPRITE_LISTS)
        self.addSpriteList(self.HOLES,       SpriteList(GameObject.OrderedUpdatesDraw()))
        self.addSpriteList(self.HERO,        SpriteList(GameObject.OrderedUpdatesDraw(), empty = False, reset = True, sorted = False))
        self.addSpriteList(self.KIDS,        SpriteList(GameObject.OrderedUpdatesDraw(), collidable = True, updateArgIndex = 0))
        self.addSpriteList(self.FLAMES,      SpriteList(GameObject.OrderedUpdatesDraw()))
        self.addSpriteList(self.GASES,       SpriteList(GameObject.OrderedUpdatesDraw()))
        self.addSpriteList(self.POWERUPS,    SpriteList(GameObject.OrderedUpdatesDraw()))
        self.addSpriteList(self.PROJECTILES, SpriteList(GameObject.OrderedUpdatesDraw()))
        self.addSpriteList(self.PKIDS,       SpriteList(GameObject.OrderedUpdatesDraw(), updateArgIndex = 0))

        self.hero_name = hero
        self.hero = Settings.HEROS[hero][1]()
        self.addSpriteToGroup(self.HERO, self.hero)

        self.egStats = EG_Stats()
        self.level = -1
        
        #collision checks
        self.addCollisionFunctions(
            [self.checkHoles,
             self.checkAttack1,
             self.checkAttack2,
             self.checkSpecialAttack1,
             self.checkSpecialAttack2,
             self.checkKidAttack,
             self.checkEat,
             self.checkPickUp,
             self.checkParachutes,
             self.checkKidThrowing,
             self.checkProjectileHit,
             self.checkGroupers])

    def reset(self):
        Engine.reset(self)
        self.run = True
        self.egStats.reset()
        self.level = -1

    #update all the sprites, check for collisions and handle them, draw on the screen
    def update(self):
        self.createFlame()
        self.createGas()
        Engine.update(self, [self.hero.rect])
        
        self.egStats.increment([Statistics.TIME, self.egStats.levelFlag(self.level)])
        
    def checkAttack1(self):
        if self.hero.isActionActive(Hero.ATTACK1):
            attackSprite = self.hero.createAttack1Sprite()
            kills = self.hitKids(pygame.sprite.spritecollide(attackSprite,self.getSpriteGroup(self.KIDS),False), self.hero.ATTACK1_DAMAGE)
            for kidtype in kills:
                self.egStats.increment([Statistics.KILLS, Statistics.PUNCH, self.egStats.levelFlag(self.level), self.egStats.enemyFlag(kidtype)], kills[kidtype])

    def checkAttack2(self):
        if self.hero.isActionActive(Hero.ATTACK2):
            attackSprite = self.hero.createAttack2Sprite()
            kills = self.hitKids(pygame.sprite.spritecollide(attackSprite,self.getSpriteGroup(self.KIDS),False), self.hero.ATTACK2_DAMAGE)
            for kidtype in kills:
                self.egStats.increment([Statistics.KILLS, Statistics.KICK, self.egStats.levelFlag(self.level), self.egStats.enemyFlag(kidtype)], kills[kidtype])
        
    def checkSpecialAttack1(self):
        flameHitDict = pygame.sprite.groupcollide(self.getSpriteGroup(self.FLAMES),self.getSpriteGroup(self.KIDS), False, False)
        for flame in flameHitDict:
            if flame.isActionActive(Flame.FLAME):
                kills = self.hitKids(flameHitDict[flame],self.hero.SPECIAL1_DAMAGE)
                for kidtype in kills:
                    self.egStats.increment([Statistics.KILLS, Statistics.FLAME, self.egStats.levelFlag(self.level), self.egStats.enemyFlag(kidtype)], kills[kidtype])

    def checkSpecialAttack2(self):
        gasHitDict = pygame.sprite.groupcollide(self.getSpriteGroup(self.GASES), self.getSpriteGroup(self.KIDS), False, False)
        for gas in gasHitDict:
            if gas.isActionActive(Gas.GAS):
                kills = self.hitKids(gasHitDict[gas],self.hero.SPECIAL2_DAMAGE)
                for kidtype in kills:
                    self.egStats.increment([Statistics.KILLS, Statistics.GAS, self.egStats.levelFlag(self.level), self.egStats.enemyFlag(kidtype)], kills[kidtype])


    def hitKids(self, kidList, totalDamage):
        kills = {}
        for kid in kidList[:]:
            if totalDamage > 0:
                self.resourceManager.playSound('Kid_Crunch')
                
                if kid.health <= totalDamage:
                    totalDamage -= kid.health
                    kid.decHealth(kid.health)
                else:
                    kid.decHealth(totalDamage)
                    totalDamage = 0
                
                if not kid.isAlive():
                    self.addDeadKid(kid)
                    kidtype = kid.__class__.__name__
                    if kidtype in kills:
                        kills[kidtype] += 1
                    else:
                        kills.update([(kidtype, 1)])
                    kid.kill()
                    self.resourceManager.playSound('Kid_Die')
        return kills

    def checkEat(self):
        if self.hero.isActionActive(Hero.EAT):
            eat = self.hero.createEatSprite()
            self.eatPowerUps(pygame.sprite.spritecollide(eat, self.getSpriteGroup(self.POWERUPS), False))
    
    def eatPowerUps(self, puList):
        for powerUp in puList[:Hero.MAX_POWERUPS_EAT]:
            flags = [Statistics.EATEN, self.egStats.powerUpFlag(powerUp.currentAnimation - powerUp.facing), self.egStats.levelFlag(self.level)]
            if powerUp.isDrugs():
                self.resourceManager.playSound(self.hero.HERO_NAME + '_Drugs')
            elif powerUp.isDeadKid():
                self.resourceManager.playSound(self.hero.HERO_NAME + '_Eat')
                flags.append(Statistics.ENEMY)
            else:
                self.resourceManager.playSound(self.hero.HERO_NAME + '_Eat')
                flags.append(Statistics.POWERUP)
            for effect in powerUp.effects:
                self.hero.addEffect(effect)
            self.egStats.increment(flags)
            powerUp.kill()
            
    def checkPickUp(self):
        if self.hero.isActionActive(Hero.PICKUP):
            pickup = self.hero.createEatSprite()
            powerUps = pygame.sprite.spritecollide(pickup, self.getSpriteGroup(self.POWERUPS), False)
            for powerUp in powerUps[:Hero.MAX_POWERUPS_PICKUP]:
                self.hero.addItem(powerUp.currentAnimation-powerUp.facing)
                powerUp.kill()

    def checkKidAttack(self):
        kids = pygame.sprite.spritecollide(self.hero,self.getSpriteGroup(self.KIDS),False)
        for kid in kids:
            if kid.isActionActive(kid.ATTACK1):
                self.resourceManager.playSound('Kid_Bite')
                self.hero.decHealth(kid.ATTACK1_DAMAGE)

    def createFlame(self):
        if self.hero.isActionActive(self.hero.FLAME):
            self.addSpriteToGroup(self.FLAMES,self.hero.createFlameSprite())
            self.resourceManager.playSound('Flame')            

    def createGas(self):
        if self.hero.isActionActive(self.hero.GAS):
            self.addSpriteToGroup(self.GASES,self.hero.createGasSprite())
            
    def checkParachutes(self):
        for kid in self.getSpriteGroup(self.PKIDS):
            if not kid.PARACHUTING:
                self.getSpriteGroup(self.PKIDS).remove(kid)
                self.addSpriteToGroup(self.KIDS,kid)
                
    def checkHoles(self):
        for hole in self.getSpriteGroup(self.HOLES):
            if hole.readyToPop and not hole.popped:
                hole.kid.setPosition((hole.rect.left+10, hole.rect.top-40))
                self.addSpriteToGroup(self.KIDS, hole.kid)
                hole.popped = True
                
    def addDeadKid(self, kid):
        """Add a new DeadKid to the field basic on the killing of the given kid"""
        powerUpType = PowerUp.__dict__['DEAD_'+kid.__class__.__name__.upper()]
        
        if kid.facing == kid.LEFT:
            facing = PowerUp.LEFT
        else:
            facing = PowerUp.RIGHT
            
        newDeadKid = PowerUp(facing, powerUpType)
        newDeadKid.setPosition((kid.rect.left-10,kid.rect.top+20))
        self.addSpriteToGroup(self.POWERUPS,newDeadKid)

    def checkKidThrowing(self):
        for kid in self.getSpriteGroup(self.KIDS):
            if kid.throwing:
                kid.throwing = False
                y = self.hero.rect.bottom - kid.rect.bottom
                x = self.hero.rect.left - kid.rect.left
                xdir = 5
                ydir = 5
                if x < 35 and x > -35: xdir = 0
                if y < 35 and y > -35: ydir = 0
                if x < 0: xdir = -xdir
                if y < 0: ydir = -ydir
                if not (x == 0 and y == 0): #don't throw if you are on top of him
                    self.addSpriteToGroup(self.PROJECTILES, Projectile((kid.rect.left+5,kid.rect.top+10),(xdir,ydir)))
                
    def checkGroupers(self):
        for kid in self.getSpriteGroup(self.KIDS):
            if kid.GROUPER:
                gkids = pygame.sprite.spritecollide(kid,self.getSpriteGroup(self.KIDS),False)
                for gkid in gkids:
                    if gkid.GROUPABLE:
                        kid.addKid(gkid)
        
    def checkProjectileHit(self):
        projectileHitDict = pygame.sprite.groupcollide(self.getSpriteGroup(self.PROJECTILES), self.getSpriteGroup(self.HERO), False, False)
        for projectile in projectileHitDict:
            self.hero.decHealth(projectile.DAMAGE)
            projectile.kill()

# SPAWN STUFFS
    def addSpawners(self):
        for enemy in Settings.ENEMIES:
            self.__dict__.update({'add'+enemy: self.createLambdaSpawner(self._addKid, Settings.ENEMIES[enemy][1])})
            self.__dict__.update({'addPara'+enemy: self.createLambdaSpawner(self._addParaKid, Settings.ENEMIES[enemy][1])})
            self.__dict__.update({'addHole'+enemy: self.createLambdaSpawner(self._addHole, Settings.ENEMIES[enemy][1])})
        i = 0
        for powerup in Settings.POWERUPS:
            self.__dict__.update({'add'+powerup: self.createLambdaSpawnerWithArg(self._addPowerUp, i)})
            i += 1

    def createLambdaSpawner(self, spawner, enemyConstructor):
        ecs = [enemyConstructor]
        return lambda: spawner(ecs[0]())

    def createLambdaSpawnerWithArg(self, spawner, arg):
        args = [arg]
        return lambda: spawner(args[0])
            
    def _addKid(self, newKid):
        newKid.setPosition(self.getRandomEdgeCoords())
        self.addSpriteToGroup(self.KIDS,newKid)
    
    def _addParaKid(self, newKid):
        newKid.setPosition(self.getRandomParaCoords())
        parachute = Parachute(newKid)
        newKid.attachGameObject(parachute)
        self.addSpriteToGroup(self.PKIDS,newKid)
    
    def getRandomParaCoords(self):
        x_min = 0
        x_max = 800
        y_min = -150
        y_max = -120
        x = random.randrange(x_min, x_max)
        y = random.randrange(y_min, y_max)
        return (x,y)

    def getRandomEdgeCoords(self):
        xy_min = 0
        x_max = 800
        y_max = 600
        x = random.randrange(800)
        y = random.randrange(600)
        choices = [(x,xy_min), (x,y_max), (xy_min,y), (x_max,y)]
        (x,y) = random.choice(choices)
        return (x,y)
        
    def _addHole(self, newKid):
        hole = Hole(newKid)
        hole.setPosition(self.getRandomInternalCoords())
        self.addSpriteToGroup(self.HOLES,hole)
        
    def getRandomInternalCoords(self):
        x = random.randrange(30,770)
        y = random.randrange(30,570)
        return (x,y)
        
    def _addPowerUp(self, powerUpType):
        newPowerUp = PowerUp(PowerUp.RIGHT, powerUpType)
        newPowerUp.setPosition(self.getRandomInternalCoords())
        self.addSpriteToGroup(self.POWERUPS,newPowerUp)
        
    #this is a little bit HACKS
    def killEG(self, screen):
        deadEG = Settings.HEROS[self.hero_name][2]()
        if self.hero.facing == self.hero.LEFT:
            deadEG.setFacing(deadEG.LEFT)
        else: deadEG.setFacing(deadEG.RIGHT)
        deadEG.currentAnimation = deadEG.DEAD + deadEG.facing
        deadEG.setPosition((self.hero.rect.left, self.hero.rect.bottom - 60))
        deadEG.update()
        self.getSpriteGroup(self.POWERUPS).draw(screen)
        self.getSpriteGroup(self.KIDS).draw(screen)
        self.getSpriteGroup(self.FLAMES).draw(screen)
        self.getSpriteGroup(self.GASES).draw(screen)
        screen.blit(deadEG.image, deadEG.rect)
        self.getSpriteGroup(self.PKIDS).draw(screen)

